import common from "shared/component/common/common";
import rMath from "shared/component/common/rMath";
import { ContainerService } from "shared/component/container/ContainerService";
import Log4R from "shared/component/log/Log4R";
import { randomProbability } from "shared/component/random/random";
import { resource } from "shared/component/resource/resource";
import FrameManager from "shared/component/time/FrameManager";
import { EffectEnum } from "shared/enums/EffectEnum";
// const loginfo = Log4R.getLog('info');
const tweenService = game.GetService('TweenService')

export class EffectServiceImpl extends ContainerService<Ctn.EffectService, Ctn.EffectEntity> implements Ctn.EffectService {
    protected serverCalls: (keyof this)[] = ["getAnim"];
    protected serviceName: keyof Ctn.Services = "effectService";
    animTable?: { animationMap: Map<string, Animation>; animWeight: Ctn.anim.AnimWeight };
    effectFolder: Instance | undefined;
    getEffectFolder() {
        if (this.effectFolder)
            return this.effectFolder
        this.effectFolder = game.Workspace.WaitForChild('effect');
        return this.effectFolder
    }
    /** 添加角色 */
    public addCharacter(character: Ctn.character.CharacterEntity) {
        character.effect = {
            effects: []
        }
    }
    private rollAnimation(animName: string) {
        const animWeight = this.animTable?.animWeight[animName];
        if (!animWeight) throw ('not find animWeight:' + animName)
        const id = randomProbability(animWeight)

        return id
    }
    /** 获取特效物体实例 */
    private createEffectInstance(path: string) {
        const realPath = "resource.effect." + path
        const res = resource.getPath(realPath, game.GetService('ReplicatedStorage'));
        if (!res) {
            throw ("not found " + realPath)
        }
        return res.Clone() as Ctn.effect.EffectModel;
    }
    /** 消除全部特效 */
    public destory(character: Ctn.character.CharacterEntity) {
        while ((character.effect?.effects.size() || 0) > 0) {
            const effect = character.effect?.effects.shift();
            effect?.destroy();
        }
    }
    public pause(character: Ctn.character.CharacterEntity, pauseEntity: TimeComponent.PauseEntity) {
        if (pauseEntity.pause) {
            this.pauseAll(character)
        } else {
            this.continue(character)
        }
    }
    /** 暂停全部特效 */
    public pauseAll(character: Ctn.character.CharacterEntity) {

        character.effect?.effects.forEach(effect => {
            effect.pause();
        })
    }
    /** 继续全部特效 */
    public continue(character: Ctn.character.CharacterEntity) {
        character.effect?.effects.forEach(effect => {
            effect.continue();
        })
    }
    /**
     * 播放特效
     * @param character 
     */
    public playEffect(character: Ctn.character.CharacterEntity, playConfig: Ctn.effect.PlayConfig) {
        if (!character.effect) {
            return
        }
        // loginfo?.info('播放特效')
        const effect: Ctn.effect.Effect = {
            playConfig,
            destroy: () => {
                effect.stop()
                const effectIndex = character.effect?.effects.findIndex(c => c === effect);
                if (typeIs(effectIndex, "number"))
                    character.effect?.effects.remove(effectIndex)
                // loginfo?.info('effect stop', character.effect)
            },
            stop: () => {
                connection.Disconnect()
                if (effectInstance) {
                    task.delay(0.3, () => {
                        effectInstance.Destroy();
                    })
                }

            },
            pause: () => {
                pause = true
                effect.animationTrack?.AdjustSpeed(0)
            },
            continue: () => {
                pause = false
                effect.animationTrack?.AdjustSpeed(effectAnimPlaySpeed)
            }
        }
        character.effect.effects.push(effect)
        let bonePart = character.character?.FindFirstChild(playConfig.followPartName);
        if (!bonePart?.IsA('BasePart')) throw (bonePart + " is not a BasePart");
        // loginfo?.info('bonePart', bonePart)
        /** 模型放大倍数 */
        const scale = playConfig.scale || 1.5;
        /** 特效播放是否暂停 */
        let pause = false;
        /** 特效物体路径 */
        const effectPath = 'trailNormal';
        /** 特效动画 */
        const trailAnimId = this.rollAnimation("slash");
        /** 特效动画最小位置 */
        const minAnimPos = 0.3;
        /** 特效持续时间 */
        const effectFrameLife = 20;
        /** 特效动画播放速度 */
        let effectAnimPlaySpeed = 1;
        /** 特效是否跟随旋转 */
        const followRotation = true;
        /** 根据跟随骨骼的moto的转动计算特效 */
        const motoAnim = "rotation";
        /** 特效倒计时 */
        let life = effectFrameLife
        /** 当前特效转动 */
        let currentTrailRotationX: number | undefined
        /** 当前特效时间位置 */
        let timepos: number | undefined
        /** 使用动态贴图 */
        const UVAnimSize = [2, 5]
        /** UV动画播放次数 */
        const UVAnimTime = 1

        /** UV动画帧长*/
        const UVStepDuration = 2;
        /** 淡出的剩余帧 */
        const UVFadeOutTime = 10
        /** UV动画实际播放的帧长：包括多次播放的 */
        let textureAnimframeTotal = ((UVAnimSize[0] * UVAnimSize[1])) * UVStepDuration * UVAnimTime
        // loginfo?.info('textureAnimframeTotal', textureAnimframeTotal)
        /** 创建特效物体 */
        const effectInstance = this.createEffectInstance(effectPath);
        /**  */
        const moto = bonePart?.FindFirstChildOfClass('Motor6D');
        /** 动画贴图 */
        let animTextures: Texture[] | undefined

        /** 特效动画根据骨骼moto转动步进 */
        let animStepWithMotoRotaion = false;
        if (motoAnim === "rotation" && moto) {
            animStepWithMotoRotaion = true;
            effectAnimPlaySpeed = 0;
        }
        effectInstance.ScaleTo(scale);
        effect.effectInstance = effectInstance;
        effectInstance.Parent = this.getEffectFolder();
        /** 特效模型的handle */
        let handle = effectInstance.Handle;
        if (handle) {
            const noInvisicam = new Instance('StringValue');
            noInvisicam.Name = 'noInvisicam';
            noInvisicam.Parent = handle
            if (UVAnimSize) {
                animTextures = [];
                effectInstance?.GetDescendants().forEach(t => {
                    if (t.IsA('Texture')) {
                        animTextures?.push(t)
                    }
                });
                animTextures?.forEach(texture => {
                    texture.LocalTransparencyModifier = 1
                })
            }
            // handle.LocalTransparencyModifier = 1
            animTextures?.forEach(texture => {
                texture.LocalTransparencyModifier = 1
            })

        }
        /** 动画播放事件 */
        const playedConn = effectInstance.AnimationController.Animator.AnimationPlayed.Connect(c => {
            // loginfo?.info('animplay!!')
            playedConn.Disconnect();
            task.delay(0.01, () => {
                // if (handle) handle.LocalTransparencyModifier = 0
                animTextures?.forEach(texture => {
                    texture.LocalTransparencyModifier = 0
                })
            })
        })
        // loginfo?.info('effectInstance', effectInstance)
        const anim = this.animTable?.animationMap.get(trailAnimId);
        if (!anim?.IsA('Animation')) {
            print("this.animTable", anim, this.animTable)
            throw ('anim:' + trailAnimId + " is undefine");
        }
        // 特效动画初始化
        effect.animationTrack = effectInstance.AnimationController.Animator.LoadAnimation(anim);
        effect.animationTrack.Play(0, 1, effectAnimPlaySpeed);
        if (minAnimPos) {
            effect.animationTrack.TimePosition = minAnimPos
        }
        /** UV动画数据 */
        let ustep = 0;
        let vstep = 0;

        /** uv动画一帧的进度 */
        let duratioinTrick = 0
        /** 帧连接器 */
        const connection = FrameManager.connect(h => {
            if (followRotation && animStepWithMotoRotaion) {
                if (moto) {
                    const [x] = moto.Transform.ToOrientation()
                    const caceRo = math.deg(x);
                    // print('caceRo', caceRo)
                    if (currentTrailRotationX === undefined) {
                        currentTrailRotationX = caceRo
                    }
                }
                effectInstance.PivotTo(bonePart.CFrame)
            } else {
                effectInstance.PivotTo(new CFrame((bonePart.CFrame).Position))
            }
            if (!pause) {

                if (UVAnimSize) {
                    textureAnimframeTotal--;
                    if (textureAnimframeTotal === UVFadeOutTime) {
                        animTextures?.forEach(c => {
                            const tweeninfoTriailHide = new TweenInfo(0.5, Enum.EasingStyle.Cubic, Enum.EasingDirection.In)
                            const t = tweenService.Create(c
                                , tweeninfoTriailHide, {
                                LocalTransparencyModifier: 1
                            })
                            t.Play();
                        })
                    }
                    if (textureAnimframeTotal > 0) {
                        if (duratioinTrick) {
                            duratioinTrick--
                        } else {
                            duratioinTrick = UVStepDuration - 1
                            if (animTextures && UVAnimSize) {

                                if ((ustep) === UVAnimSize[0]) {
                                    ustep = 0;
                                    vstep++
                                    if ((vstep) === UVAnimSize[1]) {
                                        vstep = 0;
                                    }
                                }
                                animTextures.forEach(c => {
                                    if (!c || !c.Parent) return
                                    if (UVAnimSize?.[0] && UVAnimSize?.[1]) {
                                        c.OffsetStudsU = c.StudsPerTileU / UVAnimSize[0] * (0.5 + ustep)
                                        c.OffsetStudsV = c.StudsPerTileV / UVAnimSize[1] * (0.5 + vstep)

                                    }

                                })
                                ustep++
                            }
                        }
                    }

                }

            }

            if (h) return

            if (pause) {
                return
            }
            life--;
            if (!life) {
                effect.destroy();
            }
            if (animStepWithMotoRotaion && effect.animationTrack && moto && currentTrailRotationX !== undefined) {
                const [x, y, z] = moto.Transform.ToOrientation()
                let caceRo = math.deg(x)
                if (caceRo !== currentTrailRotationX) {
                    timepos = math.round(math.abs((math.abs(currentTrailRotationX - caceRo)) % 360) / 3) / 100;
                    currentTrailRotationX = caceRo

                    let timeposFinal = effect.animationTrack.TimePosition + timepos
                    if (timeposFinal > 1.2) {
                        timeposFinal = 1.2
                    }
                    if (minAnimPos) {
                        effect.animationTrack.TimePosition = math.max(effect.animationTrack.TimePosition, timeposFinal)
                    } else {
                        effect.animationTrack.TimePosition = timeposFinal
                    }
                    // if (animTraltrack.TimePosition < 0.2) {
                    //     if (handle) handle.LocalTransparencyModifier = 1
                    // } else {
                    //     if (handle) handle.LocalTransparencyModifier = 0
                    // }
                }

            }
        })

        return effect
    }
    public getAnim(anim: string): Ctn.anim.AnimWeight | undefined {
        const result = this.invoke('getAnim', anim);
        // loginfo?.info('get weight from server:', result)
        return result
    }
    public init(): void {
        const animGroup = "trail";
        const animWeight = this.getAnim(animGroup);
        if (!animWeight) throw ('not get anim weight:' + animGroup)
        const map = new Map<string, Animation>();

        this.animTable = {
            animationMap: map,
            animWeight: animWeight,
        }

        const needLoad = []
        for (const [i, j] of pairs(animWeight)) {
            for (const [k, l] of pairs(j)) {
                let anim = this.animTable?.animationMap.get(l.id)
                if (anim?.IsA('Animation')) {
                    map.set(l.id, anim);
                    break
                }

                if (!anim) {
                    anim = new Instance('Animation');
                    anim.AnimationId = l.id
                    needLoad.push(anim)
                    map.set(l.id, anim);
                }
            }
        } const ContentProvider = game.GetService('ContentProvider')
        ContentProvider.PreloadAsync(needLoad, (contentid, ass) => {
            if (ass === Enum.AssetFetchStatus.Success) {
                // loginfo?.info('success', contentid)
            }
        });
    }
    hitEffect(config: Ctn.effect.HitEffect) {

        const part = config.p2;
        const effect = config.effect;
        const p1 = config.p1;
        const sparkxy = config.sparkxy;
        const particalPosition = new Vector3(0, p1.Position.Y + sparkxy[1] - part.Position.Y, sparkxy[0] * config.p1p2Direction)

        // const objectPositon = part.CFrame.PointToObjectSpace(particalStartPart.Position);
        // particalPosition = objectPositon.mul(rMath.vectors.xoz).Unit.mul(part.Size.X / 2);
        // particalPosition = new Vector3(particalPosition.X, objectPositon.Y, particalPosition.Z)
        let att = part.FindFirstChild('ParticalAtt') as Attachment | undefined;
        if (!att) {
            att = new Instance('Attachment');
            att.Name = 'ParticalAtt';
            att.Parent = part;
        }
        att.Position = particalPosition;
        const particalInstance = att;
        // if (particalStartPart?.IsA('BasePart')) {
        //     const objectPositon = part.CFrame.PointToObjectSpace(particalStartPart.Position);
        //     particalPosition = objectPositon.mul(rMath.vectors.xoz).Unit.mul(part.Size.X / 2);
        //     particalPosition = new Vector3(particalPosition.X, objectPositon.Y, particalPosition.Z)
        //     let att = part.FindFirstChild('ParticalAtt') as Attachment | undefined;
        //     if (!att) {
        //         att = new Instance('Attachment');
        //         att.Name = 'ParticalAtt';
        //         att.Parent = part;
        //     }
        //     att.Position = particalPosition;
        //     particalInstance = att;
        // }

        // const effectConfig = this.getHitEffectConfig(effect)

        // if (effectConfig) {

        //     effectConfig.particals?.forEach(hit => {
        //         this.particalTime(particalInstance, hit.name, hit.duration)
        //     })
        //     effectConfig.sounds?.forEach(sound => {
        //         this.$c.particleService.playSoundOn(part, sound.name)
        //     })
        // } else
        switch (effect) {
            case EffectEnum.SwordCut:
                // this.playRanomDamageVoice(part)
                // this.$c.particleService.playSoundOn(part, SoundEnum.slash_body)
                this.getPartical(particalInstance, 'slash')?.Emit(1);
                this.getPartical(particalInstance, 'blood')?.Emit(2);
                this.getPartical(particalInstance, 'spark')?.Emit(1);
                break;
            default:
                break
        }
    }
    getPartical(instance: Instance, name: string) {
        let r = instance.FindFirstChild(name)
        if (!r?.IsA('ParticleEmitter')) {
            r = resource.getPath("hitpartical." + name, game.GetService('ReplicatedStorage'))?.Clone();
            if (r) r.Parent = instance;
        }
        if (r.IsA('ParticleEmitter')) {
            r.Enabled = false;
            common.onNoParent(r, () => {
                if (r)
                    this.particalPlayingMap.delete(r)
            })
            return r
        }

    }
    /**
     * 粒子播放
     */
    particalPlayingMap = new Map<Instance, RBXScriptConnection>()
}